package com.gmrz.uaf.remote.protocol.v1.processor;

import com.gmrz.service.challengestore.ChallengeStoreException;
import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.db.DAOException;
import com.gmrz.uaf.db.UAFDBConnectionMgr;
import com.gmrz.uaf.policy.verification.PolicyVerificationException;
import com.gmrz.uaf.protocol.v1.json.UAFSchemaBuilder;
import com.gmrz.uaf.protocol.v1.processor.UAFRegFinishProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.*;
import com.gmrz.uaf.protocol.v1.schema.*;
import com.gmrz.uaf.protocol.v1.validaton.ValidationException;
import com.gmrz.uaf.protocol.v1.validaton.Validator;
import com.gmrz.uaf.remote.plugin.AuthServicePluginManager;
import com.gmrz.uaf.remote.protocol.v1.validation.reg.UAPRegResponseValidator;
import com.gmrz.uaf.server.ServerContext;
import com.gmrz.uaf.servlet.v1.request.RegSendContext;
import com.gmrz.uas.plugin.authservice.AuthServiceData;
import com.gmrz.uas.plugin.authservice.AuthServicePlugin;
import com.gmrz.uas.plugin.exception.PluginException;
import com.gmrz.uas.plugin.exception.PluginGoOnBusinessException;
import com.gmrz.util.Convert;
import com.gmrz.util.CryUtil;
import com.gmrz.util.UUIDGenerator;
import com.gmrz.util.db.DBUtil;
import com.google.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by gongyuqiang on 2017/5/2.
 */
public class UAPRegFinishProcessor extends UAFRegFinishProcessor {
    private static final Logger LOG = LogManager.getLogger(UAPRegFinishProcessor.class);

    AuthServicePluginManager serviceManager;

    @Inject
    public void setServiceManager(AuthServicePluginManager serviceManager) {
        this.serviceManager = serviceManager;
    }

    /**
     * 远程注册
     *
     * @param response
     * @param hashedUserName
     * @return
     * @throws ValidationException
     * @throws ChallengeStoreException
     * @throws PolicyVerificationException
     * @throws DAOException
     * @throws PossibleReplayAttackException
     * @throws RegistrationFailedException
     * @throws PolicyNotFoundException
     */
    @Override
    public UAFRegFinishResultBean process(RegistrationResponse response, String hashedUserName, RegSendContext regSendContext)
            throws ValidationException, ChallengeStoreException, PolicyVerificationException, DAOException, PossibleReplayAttackException, RegistrationFailedException, PolicyNotFoundException {
        String appID = regSendContext.getAppID();
        String authType = regSendContext.getAuthType();
        String transType = regSendContext.getTransType();
        String opType = regSendContext.getOpType();
        DeviceDetails deviceDetails = regSendContext.getDevices();
        String mobile = regSendContext.getMobile();
        String rf1 = regSendContext.getRf1();
        String rf2 = regSendContext.getRf2();
        LOG.info("Validating registration response.");
        Validator<RegistrationResponse> validator = new UAPRegResponseValidator(this.serverConfig,
                Constants.Operation.REG, appID,transType);

        ServerData serverData = response.getOperationHeader().getServerData();
        if (this.getServerConfig().getPerformModel() < Constants.PERFORM_MODEL_LEVEL_2 && !ServerContext.getServerContext().isPerform()) {
            validator.validate(response);
            
            checkForReplayAttack(serverData);

            List<Extension> exts = response.getOperationHeader().getExtensions();
            processExtensions(exts);

            TLSData tlsData = response.getFinalChallengeParams().getTLSData();
            processTLSData(tlsData);
        }

        String policyName = serverData.getPolicyName();//policy_appID_authType
        String custNo = serverData.getUserName();//客户号

        AuthenticatorRegistration registration = response.getRegistration();
        if (registration == null) {
            String msg = "No AuthenticatorRegistration found in the registration response for user[" + custNo + "], policy["
                    + policyName + "].";

            logAndThrowRFE(msg);
        }

        String aaid = registration.getAaid();
        Policy p = this.policyManager.getNamedPolicy(authType, appID, transType);
        LOG.info("Policy:" + UAFSchemaBuilder.getGson().toJson(p));
        if (p == null || !isAcceptedAaid(p, aaid)) {
            LOG.error("Requested policy {} is not available on server", "POLICY_" + appID + "_" + authType);
            throw new PolicyNotFoundException(UAFErrorCode.BIZ_POLICY_NOT_FOUND);
        }

        boolean isSucceed = false;
        DeregisterationRequest deregisterationRequest = null;
        Connection conn = null;
        try {
            AuthenticatorSpec spec = getAuthenticatorSpec(aaid);
            if (spec == null) {
                LOG.error("Could not locate metadata for AAID[{}]", aaid);
                throw new MetadataNotFoundException(UAFErrorCode.DB_QUERY_METADATA_FAILED,
                        "Could not locate metadata for AAID[" + aaid + "]");
            }

            Authenticator token = processAuthenticator(response, registration, spec, hashedUserName, custNo,
                    appID, deviceDetails.getDeviceID(), authType, transType);
            token.setMobile(mobile);
            token.setRf1(rf1);
            token.setRf2(rf2);

            conn = UAFDBConnectionMgr.getConnection(false);
            saveAuthenticator(token, spec, opType, conn);


            deviceDetails.setAppID(appID);
            deviceDetails.setUserName(hashedUserName);
            deviceDetails.setAuthType(authType);
            deviceDetails.setTransType(transType);
            deviceDetails.setStatus(Constants.AuthenticatorStatus.ACTIVE.getStatus());
            saveDevice(deviceDetails, opType, conn);

            String pluginKeyStr = response.getOperationHeader().getApplicationID() + Convert.toBase64(serverData.getChallenge());
            LOG.debug("pluginKeyStr:" + pluginKeyStr);
            List<AuthServiceData> contents = new ArrayList<AuthServiceData>();
            for (Extension ext : registration.getExtensions()) {
                AuthServiceData content = new AuthServiceData();
                LOG.debug("id=" + ext.getId());
                LOG.debug("data=" + ext.getData());
                content.setId(ext.getId());
                content.setContent(Convert.fromBase64(ext.getData()));
                content.setKey(pluginKeyStr.getBytes());
                contents.add(content);
            }
            AuthServicePlugin service = serviceManager.getServicePlugin(aaid, authType);
            String pluginUserName = CryUtil.getSHA(hashedUserName + "_" + appID + "_" + authType + "_" + transType);
            LOG.debug("pluginusername=" + hashedUserName + "_" + appID + "_" + authType + "_" + transType + ",pluginUserNameHash:" + pluginUserName);
            service.register(pluginUserName, contents);
            deregisterationRequest = toDelete(hashedUserName, appID, deviceDetails.getDeviceID(), authType, transType, conn);
            conn.commit();
            isSucceed = true;
            if (serverConfig.getAppIdUrlBoolean(appID,transType)) {
                setFacetsAaid(deregisterationRequest, appID);
            }
            UAFRegFinishResultBean uafRegFinishResultBean = getResultBean(deregisterationRequest,null);
            return uafRegFinishResultBean;
        } catch (SQLException sqle) {
            LOG.error("Failed to  connect to database, ", sqle);
            throw new DAOException(UAFErrorCode.DB_DATABASE_CONNECTION_ERROR,
                    "Failed to connect to database", sqle);
        } catch (PluginGoOnBusinessException e) {
            throw e;
        } catch (PluginException e) {
            LOG.error(e.getErrorMsg());
            throw new AuthenticationFailedException(e.getErrorMsg());
        } catch (Exception e) {
            e.printStackTrace();
            LOG.error(e);
            throw new RegistrationFailedException(e);
        } finally {
            if (!isSucceed) {
                LOG.error("Failed to Register....");
                DBUtil.rollback(conn);
            }

            DBUtil.close(conn);
        }
    }

    /**
     * 封装注册信息
     *
     * @param registrationResponse
     * @param registration
     * @param spec
     * @param hashedUserName
     * @param custNo
     * @param appID
     * @param deviceID
     * @param authType
     * @param transType
     * @return
     * @throws ValidationException
     * @throws DAOException
     * @throws PolicyVerificationException
     * @throws MetadataNotFoundException
     * @throws TLVParsingException
     * @throws CertificateValidationException
     * @throws SignatureValidationException
     * @throws IOException
     * @throws ChallengeStoreException
     */
    private Authenticator processAuthenticator(RegistrationResponse registrationResponse,
                                               AuthenticatorRegistration registration, AuthenticatorSpec spec,
                                               String hashedUserName, String custNo, String appID,
                                               String deviceID, String authType, String transType) throws ValidationException,
            DAOException, PolicyVerificationException, MetadataNotFoundException, TLVParsingException,
            CertificateValidationException, SignatureValidationException, IOException, ChallengeStoreException {
        AAID aaid = spec.getAAID();

        List<Extension> exts = registration.getExtensions();
        if ((exts != null) && (!exts.isEmpty())) {
            processExtensions(exts);
        }
        Authenticator retval = new Authenticator().withAAID(aaid).withKeyID(new KeyID().withId(UUIDGenerator.generateID().getBytes()))
                .withKeyFormat(spec.getKeyFormat()).withAuthenticatorVersion(spec.getAuthenticatorVersion().shortValue());
        retval.setID(UUIDGenerator.generateID());
        retval.setAppID(appID);
        retval.setDeviceID(deviceID);
        retval.setAuthType(authType);
        retval.setTransType(transType);
        retval.setPubKey(" ");
        retval.setStatus(registrationResponse.getRegistrationStatus().getStatus());
        retval.setCustNo(custNo);
        retval.setUserName(hashedUserName);
        retval.setMetadata(spec);
        retval.setRegCounter(0);
        return retval;
    }
}
